﻿///////////////////////////////////////////////////////////////////////////////
//
// This file is part of the BassDrop project
//   https://code.google.com/p/bass-drop/
//
//
// Creator:	Gergely Krajcsovszki (krajcsovszkig@hotmail.com)
// Date:	2014-03-16 18:10
//
//
// Copyright © 2013-2014. Gergely Krajcsovszki
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
///////////////////////////////////////////////////////////////////////////////
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace BassDrop.Core.Common.Utilities
{
	/// <summary>
	/// Contains extension methods to help circumvent the lack of some thread related
	/// features in Portable Class Libraries
	/// </summary>
	public static class ThreadingUtils
	{
		/// <summary>
		/// Invokes the passed callback on this SynchronizationContext and waits for its execution. Can be used even if
		/// SynchronizationContext.Send is not available. Throws the exceptions thrown in the delegate.
		/// </summary>
		/// <param name="context">the context to run the method</param>
		/// <param name="contextManagedThreadId">the managed thread id of the thread associated with the passed context</param>
		/// <param name="d">the method to run</param>
		/// <param name="state">the parameter of the method to run</param>
		/// <param name="lockObject">the object to lock on</param>
		public static void InvokeSynchronized( this SynchronizationContext context, int contextManagedThreadId, SendOrPostCallback d, object state, object lockObject )
		{
			if ( !context.Match( SynchronizationContext.Current, contextManagedThreadId, Thread.CurrentThread.ManagedThreadId ) )
			{
				lock ( lockObject )
				{
					Exception error = null;
					bool block = true;

					// replicate SynchronizationContext.Send with .Post as Send is obsolete in the Portable Class Library
					context.Post( ( o ) =>
						{
							lock ( lockObject )
							{
								try
								{
									d( o );
								}
								catch ( Exception exc )
								{
									error = exc; // TODO: így kimegy az error?
								}
								finally
								{
									block = false;
									Monitor.PulseAll( lockObject );
								}
							}
						},
					state );

					while ( block )
					{
						// BUG: vannak más lockok is, amiket nem ad át, így deadlockok keletkeznek
						// vagy nagyon figyelni, hogy csak a lockobject lehessen lezárva, vagy inkább post + közvetlen hívás arra, amit nem lehet késleltetni
						Monitor.Wait( lockObject, 1000 ); // TODO: exitelni minden lockot előtte, aztán enterelni utána
					}

					if ( error != null )
					{
						throw error;
					}
				}
			}
			else
			{
				d( state );
			}
		}

		/// <summary>
		/// Checks if the two SynchronizationContexts refer to the same thread
		/// </summary>
		/// <param name="sc1"></param>
		/// <param name="sc2"></param>
		/// <param name="sc1ManagedThreadId">the managed thread id of the thread associated with sc1</param>
		/// <param name="sc2ManagedThreadId">the managed thread id of the thread associated with sc2</param>
		/// <returns></returns>
		public static bool Match( this SynchronizationContext sc1, SynchronizationContext sc2, int sc1ManagedThreadId, int sc2ManagedThreadId )
		{
			if ( sc2 == null )
			{
				return false;
			}
			else
			{
				return sc1ManagedThreadId == sc2ManagedThreadId;
			}
		}

		/// <summary>
		/// Block the execution of the current thread for the given amount of milliseconds
		/// </summary>
		/// <param name="timeoutMillis"></param>
		public static void Wait( int timeoutMillis )
		{
			using ( var waiter = new ManualResetEvent( false ) )
			{
				waiter.WaitOne( timeoutMillis );
			}
		}

		/// <summary>
		/// Substitute for the Task.FromResult method (unavailable in PCL)
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="value"></param>
		/// <returns></returns>
		/// <remarks> From http://stackoverflow.com/a/14244239/301134 </remarks>
		public static Task<T> TaskFromResult<T>( T value )
		{
			var tcs = new TaskCompletionSource<T>();
			tcs.SetResult( value );
			return tcs.Task;
		}

		/// <summary>
		/// Returns a completed task for use in synchronous overrides of methods intended to be async
		/// </summary>
		/// <returns></returns>
		public static Task CompletedTask()
		{
			return TaskFromResult( 0 );
		}
	}
}
